package com.guo.mixframe.framework.receivers

import com.guo.mixframe.framework.configs.CoreConfig
import com.guo.mixframe.framework.exception.CoreException
import com.guo.mixframe.framework.log.LogPrint
import com.guo.mixframe.framework.protocol.GameReqHeader
import com.guo.mixframe.framework.protocol.GameRespHeader
import com.guo.mixframe.framework.servers.LoginDBCreator
import com.guo.mixframe.framework.session.SessionManager
import com.guo.mixframe.framework.utils.ProtobufUtils
import com.guo.mixframe.framework.utils.StringHelper
import io.netty.buffer.ByteBuf
import io.netty.buffer.PooledByteBufAllocator
import io.vertx.core.Handler
import io.vertx.core.eventbus.DeliveryOptions
import io.vertx.core.eventbus.EventBus
import io.vertx.core.eventbus.ReplyException
import java.util.ArrayDeque
import java.util.Queue
import java.util.concurrent.ConcurrentHashMap

/**
 * @author  gx
 * @description
 */
class GameMessageHelper(private val eventBus: EventBus) {

  private val clientRequests = ConcurrentHashMap<String, Queue<GameMessage>>()

  suspend fun handleReceive(buffer: ByteBuf?, client: GameClient?) {
    if (buffer == null || client == null) return
    val header = readHeader(buffer)
    val remoteAddress = client.remoteAddress
    var sessionObj = SessionManager.getInstance().check(header.session)
    if (sessionObj == null) {
      sessionObj = LoginDBCreator.createLoginDB().getSession(header.session!!)
      SessionManager.getInstance().updateSession(sessionObj!!)
    }
    client.sessionId = header.session
    val body = readBody(buffer)
    val clientRequest = GameMessage(
      header.reqId,
      header.url,
      header.lan,
      buildDeliveryOption(header, remoteAddress),
      body,
      client,
      sessionObj.getUniqueId()
    )
    if ("ping" == clientRequest.url) {
      sendToClient(client, GameRespHeader(header.reqId), null)
    } else {
      checkValid(clientRequest)
      val uniqueId: String = clientRequest.uniqueId!!
      if (StringHelper.isNullOrEmpty(uniqueId)) {
        throw CoreException().throwException(CoreException.SessionExpired)
      } else {
        sessionObj.opTime = System.currentTimeMillis()
        if (addClinentRequest(uniqueId, clientRequest)) {
          //从玩家消息队列中取一个消息进行处理
          processNextClientRequest(uniqueId)
        }
      }
    }
  }

  private fun processNextClientRequest(uniqueId: String) {
    //取队列头部消息
    val message = peekGameMessage(uniqueId)
    if (message != null) {
      //处理玩家请求
      processOneClientRequest(message) {
        //移除队列头消息
        pollGameMessage(uniqueId)
        //递归调用处理下一个请求
        processNextClientRequest(uniqueId)
      }
    }
  }

  private fun processOneClientRequest(message: GameMessage, callBack: Handler<GameClient>?) {
    val options: DeliveryOptions = message.options!!
    val body: ByteArray = message.body
    val client:GameClient = message.client
    val requestId:Long = message.requestId
    val lan:String = message.language!!
    eventBus.request(message.url!!,body,options){
      resp ->
      if (resp.succeeded()) {
        sendToClient(client, GameRespHeader(requestId), resp.result().body()!!)
      } else {
        var failureCode = (resp.cause() as ReplyException).failureCode()
        if (failureCode < 0) {
          LogPrint.exceptionLogger.info("eventbus reply fail", resp.cause())
        }
        sendToClient(client, GameRespHeader(requestId), null)
      }
      callBack?.handle(client)
    }

  }


  /**
   * 取队列头部消息
   */
  private fun peekGameMessage(uniqueId: String): GameMessage? {
    synchronized(uniqueId.intern()) {
      val queue = clientRequests[uniqueId]
      if (queue == null || queue.size <= 0) {
        return null
      }
      return queue.peek()
    }
  }

  /**
   * 移除队列头消息
   */
  private fun pollGameMessage(uniqueId: String) {
    synchronized(uniqueId.intern()) {
      val queue = clientRequests[uniqueId]
      if (queue == null || queue.size <= 0) {
        return
      }

      queue.poll()
    }
  }

  private fun addClinentRequest(uniqueId: String, clientRequest: GameMessage): Boolean {
    synchronized(uniqueId.intern()) {
      var queue: Queue<GameMessage>? = clientRequests[uniqueId]
      if (queue == null) {
        queue = ArrayDeque()
        clientRequests[uniqueId] = queue
      }
      if (queue.size == 0) {
        queue.add(clientRequest)
        return true
      }
      queue.add(clientRequest)
      return false
    }
  }

  /**
   *  校验消息类型
   */
  private fun checkValid(clientRequest: GameMessage) {


  }

  fun sendToClient(client: GameClient, header: GameRespHeader, body: ByteArray?) {
    val buffer = buildNetMessage(client, body, client.isDataWithLength)
    client.sendData(buffer)
  }

  private fun buildNetMessage(header: GameClient, body: ByteArray?, dataWithLength: Boolean): ByteBuf {
    val encodedData: ByteArray = ProtobufUtils.bean2Byte(header)
    var dataLength = encodedData.size + 4 /* Header length size */
    //tcp消息头多4个字节表示消息总字节数
    if (dataWithLength) {
      dataLength += 4
    }

    val protocolValid = body != null && body.isNotEmpty()

    if (protocolValid) {
      dataLength += body!!.size
    }

    val buf = PooledByteBufAllocator.DEFAULT.buffer(dataLength)
    //tcp消息头多4个字节表示消息总字节数
    if (dataWithLength) {
      buf.writeIntLE(dataLength)
    }
    //消息头字节数
    buf.writeIntLE(encodedData.size)
    //消息头
    buf.writeBytes(encodedData)
    //消息体
    if (protocolValid) {
      buf.writeBytes(body)
    }

    return buf
  }


  private fun buildDeliveryOption(request: GameReqHeader, remoteAddress: String): DeliveryOptions? {
    val options = DeliveryOptions()
    options.sendTimeout = CoreConfig.getInstance().checkTimeOut().toLong()
    options.addHeader("url", request.url)
    options.addHeader("reqId", request.reqId.toString())
    options.addHeader("ip", remoteAddress)
    if (!request.session.isNullOrEmpty()) {
      options.addHeader("session", request.session)
    }
    if (!request.lan.isNullOrEmpty()) {
      options.addHeader("lan", request.lan)
    }
    return options
  }


  private fun readBody(buffer: ByteBuf): ByteArray {
    val body = ByteArray(buffer.readableBytes())
    if (body.isNotEmpty()) {
      buffer.readBytes(body)
    }
    return body
  }

  private fun readHeader(buffer: ByteBuf): GameReqHeader {
    val length = buffer.readIntLE()
    if (length > buffer.readableBytes()) {
      throw CoreException().throwException(CoreException.UnknownError)
    } else {
      val encodeData = ByteArray(length)
      buffer.readBytes(encodeData)
      val headerClass = GameReqHeader::class.java
      val header = ProtobufUtils.byteToBean(encodeData, headerClass)
      return header
    }
  }


}
